<?php

namespace App\Models\Trip\SnapTaskUpgradePay;

use App\Exceptions\OrderPay\BusinessMustBeRefundException;
use App\Exceptions\OrderPay\PayInfoNotFoundException;
use App\Models\Common\Channel\Channel;
use App\Models\Task\SnapTasks\Repositories\Interfaces\SnapTaskRepositoryInterface;
use App\Models\Task\SnapTasks\SnapTask;
use App\Models\Trip\OrderPay\OrderPay;
use App\Models\Trip\OrderPay\Repositories\Interfaces\OrderPayRepositoryInterface;
use App\Models\Trip\TripOrderCosts\Repositories\Interfaces\TripOrderCostsRepositoryInterface;
use App\Models\Trip\TripOrderCosts\Repositories\TripOrderPassengerCostsRepository;
use App\Models\Trip\TripOrderCosts\TripOrderCost;
use App\Trip\Code;
use App\Trip\Enums\OrderBusiness;
use App\Trip\Enums\TripTypes;
use App\Trip\Train\Order\Impl\PiaoChiTrainSnapImpl;
use App\Trip\Traits\Pay\SubPayModelAttributeTrait;
use App\Trip\Wechat\Pay\PayRes\PayResult;
use App\Trip\Wechat\Pay\WechatPay;
use Eloquent;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Throwable;

/**
 * App\Models\Trip\SnapTaskUpgradePay\SnapTaskUpgradePay
 *
 * @property int                $id
 * @property string             $user_id       用户
 * @property string             $pay_sn        支付单号
 * @property string             $task_id       任务id
 * @property string             $trade_sn      支付交易单号
 * @property string             $pay_type      支付类型 1 微信支付
 * @property string             $pay_title     支付标题
 * @property string             $pay_amount    支付金额
 * @property string             $receipts      实收金额
 * @property int                $pay_state     支付状态 0待支付 1已支付 3已取消 2 失败
 * @property string             $trade_desc    交易描述
 * @property string             $cancel_reason 取消原因
 * @property string             $failed_reason 失败原因
 * @property string             $mchid         支付商戶id
 * @property string             $appid         appid
 * @property string|null        $expired_at    交易结束时间
 * @property Carbon|null        $created_at
 * @property Carbon|null        $updated_at
 * @method static Builder|SnapTaskUpgradePay newModelQuery()
 * @method static Builder|SnapTaskUpgradePay newQuery()
 * @method static Builder|SnapTaskUpgradePay query()
 * @method static Builder|SnapTaskUpgradePay whereAppid($value)
 * @method static Builder|SnapTaskUpgradePay whereCancelReason($value)
 * @method static Builder|SnapTaskUpgradePay whereCreatedAt($value)
 * @method static Builder|SnapTaskUpgradePay whereExpiredAt($value)
 * @method static Builder|SnapTaskUpgradePay whereFailedReason($value)
 * @method static Builder|SnapTaskUpgradePay whereId($value)
 * @method static Builder|SnapTaskUpgradePay whereMchid($value)
 * @method static Builder|SnapTaskUpgradePay wherePayAmount($value)
 * @method static Builder|SnapTaskUpgradePay wherePaySn($value)
 * @method static Builder|SnapTaskUpgradePay wherePayState($value)
 * @method static Builder|SnapTaskUpgradePay wherePayTitle($value)
 * @method static Builder|SnapTaskUpgradePay wherePayType($value)
 * @method static Builder|SnapTaskUpgradePay whereReceipts($value)
 * @method static Builder|SnapTaskUpgradePay whereTaskId($value)
 * @method static Builder|SnapTaskUpgradePay whereTradeDesc($value)
 * @method static Builder|SnapTaskUpgradePay whereTradeSn($value)
 * @method static Builder|SnapTaskUpgradePay whereUpdatedAt($value)
 * @method static Builder|SnapTaskUpgradePay whereUserId($value)
 * @property-read OrderPay|null $order_pay
 * @mixin Eloquent
 */
class SnapTaskUpgradePay extends Model
{
    use HasFactory, SubPayModelAttributeTrait;

    public    $fillable
                     = [
            "user_id",
            "pay_sn",
            "task_id",
            "trade_sn",
            "pay_type",
            "pay_title",
            "pay_amount",
            "receipts",
            "pay_state",
            "trade_desc",
            "cancel_reason",
            "failed_reason",
            "mchid",
            "appid",
            "expired_at",
        ];
    protected $table = 'snap_task_upgrade_pay';

    public function order_pay()
    {
        return $this->hasOne(OrderPay::class, 'pay_sn', 'pay_sn');
    }

    public function getReceipts(): float
    {
        return $this->getAttribute('receipts');
    }

    public function addPay(SnapTask $task, WechatPay $pay, Channel $channel): self
    {
        if ($pay_info = $this->findOneByTask($task)) {
            return $pay_info;
        }
        $pay_amount = $task->calcUpgradeLevelAmount();
        $pay_info   = null;
        $addData    = [
            "user_id"    => $task->getUserId(),
            "pay_sn"     => generate_order_id(TripTypes::TRAIN, OrderBusiness::PAY),
            "task_id"    => $task->getTaskId(),
            "pay_type"   => OrderPay::PAY_TYPE_WECHAT,
            "pay_title"  => "升级加速包",
            "pay_amount" => $pay_amount,
            "pay_state"  => OrderPay::PAY_STATE_WAITING,
            "mchid"      => $pay->getMchid(),
            "appid"      => $channel->getAppId(),
        ];
        DB::transaction(function () use (&$pay_info, $addData) {
            $pay_info = $this->create($addData);
            // 添加 orderPay 记录 避免业务单号冲突 ,便于后期退款
            $orderPayRepo = app(OrderPayRepositoryInterface::class);
            $orderPayRepo->createTrainSnapTaskUpgradePay($pay_info);
        });
        return $pay_info;
    }

    public function findOneByTask(SnapTask $task)
    {
        return $this->where('task_id', $task->getTaskId())->first();
    }

    public function getTaskId()
    {
        return $this->getAttribute('task_id');
    }

    /**
     * @throws Exception|Throwable
     */
    public function updatePayByPayResult(string $pay_sn, PayResult $result)
    {
        $pay_info = $this->findOneByPaySn($pay_sn);
        if (!$pay_info) {
            throw new PayInfoNotFoundException($result, '支付单数据不存在', "upgrade snap level :updatePayByPayResult,支付回调-支付单数据不存在：{$pay_sn}");
        }
        $task_id = $pay_info->getTaskId();
        // 如果支付成功
        if ($pay_info->isPaySuccess()) {
            return true;
        }
        $orderPayRepo = app(OrderPayRepositoryInterface::class);
        if ($result->isSuccess()) {
            $taskRepo  = app(SnapTaskRepositoryInterface::class);
            $costRepo  = app(TripOrderCostsRepositoryInterface::class);
            $task_info = $taskRepo->findOneByTaskId($task_id);
            if ($task_info->isPaidHighLevel()) {
                Log::info("估计是重复执行:当前任务:{$task_id},抢票等级,{$task_info->getSnapLevel()}");
                $this->updateOne([
                    'pay_state'  => OrderPay::PAY_STATE_SUCCESS,
                    'trade_sn'   => $result->getTransactionId(),
                    'receipts'   => $result->getPayAmount(),
                    'trade_desc' => $result->getTradeStateDesc(),
                ],
                    $pay_info);

                $orderPayRepo->updateOne($pay_info->order_pay, [
                    'pay_state'   => OrderPay::PAY_STATE_SUCCESS,
                    'trade_sn'    => $result->getTransactionId(),
                    'real_amount' => $result->getPayAmount(),
                ]);
                return true;
            }
            try {
                $order = $task_info->getTrainOrderInfo();
                DB::transaction(function () use ($taskRepo, $costRepo, $order, $result, $task_info, $pay_info, $orderPayRepo) {
                    // 更新抢票等级
                    $update          = $taskRepo->updateTaskSnapLevel($task_info, SnapTask::SNAP_LEVEL_PAY_HIGH);
                    $passenger_count = $task_info->getPassengersCount();
                    $passengers      = $task_info->getPassengers();
                    $upgrade_price   = $task_info->getUpgradeHighLevelPrice();
                    $cost_type       = TripOrderCost::COST_TYPE_SNAP_HIGH_LEVEL_SERVICE_FEE;
                    $cost_name       = TripOrderCost::COST_TYPE_DESC[TripOrderCost::COST_TYPE_SNAP_HIGH_LEVEL_SERVICE_FEE];
                    $costRepo->addOne($order, [
                        "type"        => $cost_type,
                        "parent_type" => $cost_type,
                        "name"        => $cost_name,
                        "price"       => $upgrade_price, // 单价
                        "count"       => $passenger_count,
                        "pay_sn"      => $pay_info->getPaySn(),
                    ]);
                    $passenger_costs = [];
                    foreach ($passengers as $passenger) {
                        $passenger_costs [] = [
                            "user_id"      => $order->getUserId(),
                            "trip_id"      => $order->getTripId(),
                            "trip_type"    => $order->getTripType(),
                            "order_sn"     => $order->getOrderSn(),
                            "passenger_id" => $passenger->getPassengerId(),
                            "sequence"     => $passenger->getSequence(),
                            "type"         => $cost_type,
                            "parent_type"  => $cost_type,
                            "pay_sn"       => $pay_info->getPaySn(),
                            "price"        => $upgrade_price,
                            "name"         => $cost_name,
                            "detail"       => "",
                        ];
                    }
                    // passenger costs
                    $passengerCostRepo = app(TripOrderPassengerCostsRepository::class);
                    $passengerCostRepo->addMore($passenger_costs);
                    $this->updateOne([
                        'pay_state'  => OrderPay::PAY_STATE_SUCCESS,
                        'trade_sn'   => $result->getTransactionId(),
                        'receipts'   => $result->getPayAmount(),
                        'trade_desc' => $result->getTradeStateDesc(),
                    ],
                        $pay_info);

                    $orderPayRepo->updateOne($pay_info->order_pay, [
                        'pay_state'   => OrderPay::PAY_STATE_SUCCESS,
                        'trade_sn'    => $result->getTransactionId(),
                        'real_amount' => $result->getPayAmount(),
                    ]);
                });

                try {
                    $snap = new PiaoChiTrainSnapImpl($order);
                    $snap->snap();
                } catch (Exception $exception) {
                    Log::error("升级抢票抢票任务开始失败,t:{$task_info->getTaskId()},o:{$order->getOrderSn()}".$exception->getMessage());
                }
            } catch (Exception $e) {
                if ($e->getCode() == Code::TRAIN_SNAP_TASK_UPGRADE_HIGH_ERROR) {
                    $this->updateOne([
                        'pay_state'     => OrderPay::PAY_STATE_NEED_REFUND,
                        'failed_reason' => "支付状态".$result->getTradeStateDesc().",异常:{$e->getMessage()}",
                    ],
                        $pay_info);

                    $orderPayRepo->updateOne($pay_info->order_pay, [
                        'pay_state'     => OrderPay::PAY_STATE_NEED_REFUND,
                        'failed_reason' => "支付状态".$result->getTradeStateDesc().",异常:{$e->getMessage()}",
                    ]);
                    throw new BusinessMustBeRefundException($result, $e->getMessage(), $e->getMessage(), $e->getCode());
                }
                // 这里只标记支付成功,和错误信息
                Log::error("此处业务失败了可能是BUG,服务挂了:{$pay_sn},{$e->getMessage()}");
                $this->updateOne([
                    'pay_state'     => OrderPay::PAY_STATE_SUCCESS_BUSINESS_ERROR, // 标记业务失败 以便于重试
                    'trade_sn'      => $result->getTransactionId(),
                    'receipts'      => $result->getPayAmount(),
                    'trade_desc'    => $result->getTradeStateDesc(),
                    'failed_reason' => Str::limit($e->getMessage(), 180),
                ],
                    $pay_info);

                $orderPayRepo->updateOne($pay_info->order_pay, [
                    'pay_state'     => OrderPay::PAY_STATE_SUCCESS_BUSINESS_ERROR,
                    'trade_sn'      => $result->getTransactionId(),
                    'real_amount'   => $result->getPayAmount(),
                    'failed_reason' => Str::limit($e->getMessage(), 180),
                ]);
            }
        } else {
            $this->updateOne([
                'pay_state'     => OrderPay::PAY_STATE_FAILED,
                'failed_reason' => $result->getTradeStateDesc(),
            ],
                $pay_info);

            $orderPayRepo->updateOne($pay_info->order_pay, [
                'pay_state'     => OrderPay::PAY_STATE_FAILED,
                'failed_reason' => $result->getTradeStateDesc(),
            ]);
        }
    }

    public function findOneByPaySn(string $pay_sn): ?self
    {
        return $this->where('pay_sn', $pay_sn)->first();
    }

    public function updateOne(array $data, $model = null): bool
    {
        if ($model instanceof self) {
            return $model->update($data);
        }
        return $this->update($data);
    }

    private function markNeedRefundAndRefund()
    {

    }
}
